/*
 *	Copyright (C) Ergobyte Informatics S.A. - All Rights Reserved
 *
 * 	This material is proprietary to Ergobyte. The methods and techniques described herein are considered trade secrets
 *	and/or confidential. Reproduction or distribution, in whole or in part, is forbidden except by express written permission.
 */
package com.ergobyte.servo.client;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.StreamCorruptedException;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONTokener;

import com.ergobyte.servo.client.Result.JsonResult;
import com.ergobyte.servo.client.Result.StatusResult;
import com.ergobyte.servo.client.api.ErgobyteAPI.S_servo;

public class Connection {

	// Constants

	private static final int IDLE_TIMEOUT = 5 * 60 * 1000; // 5 minutes

	// Fields

	private String hostName;
	private boolean useSsl;
	private String clientId;
	private long lastResponseTime;
	private TotpGenerator totpGenerator;
	private String installationId;

	// Constructor

	public Connection(String hostName, boolean useSsl, String clientId, String installationId, String clientSecret) {
		this.hostName = hostName;
		this.useSsl = useSsl;
		this.clientId = clientId;
		this.installationId = installationId;
		this.totpGenerator = new TotpGenerator(clientSecret);
	}

	// Public methods

	public long getIdleTime() {
		return System.currentTimeMillis() - lastResponseTime;
	}

	public void authorizeClient() throws IOException {
		long challenge = -1l;
		Result result = callService0(S_servo.name, S_servo.C_getChallenge);
		if(result instanceof JsonResult && ((JsonResult)result).getValue() instanceof String)
			challenge = Long.valueOf(((String)((JsonResult)result).getValue()));
		String token = totpGenerator.generateToken(challenge);
		result = callService0(S_servo.name, S_servo.C_authorizeClient,
				S_servo.P_authorizeClient_client, clientId,
				S_servo.P_authorizeClient_installation, installationId,
				S_servo.P_authorizeClient_token, token);
		if(result.isOK()) return;
		throw new IOException(String.format("Error %d while authorizing client", result.getStatusCode()));
	}

	public Result callService(String serviceName, String commandName, String ... query) throws IOException {
		if(getIdleTime() > IDLE_TIMEOUT) authorizeClient();
		Result result = callService0(serviceName, commandName, query);
		lastResponseTime = System.currentTimeMillis();
		return result;
	}

	// Internal stuff

	private Result callService0(String serviceName, String commandName, String ... nameValues) throws IOException {
		String uri = String.format("%s://%s/service/%s/%s", useSsl ? "https" : "http", hostName, serviceName, commandName);
		HttpPost httpPost = new HttpPost(uri);
		if(nameValues != null)
			httpPost.setEntity(makeUrlEncodedEntity(nameValues));
		try {
			HttpResponse result = HttpClientInstance.get().execute(httpPost);
			HttpEntity entity = result.getEntity();
			try {
				StatusLine statusLine = result.getStatusLine();
				return processResponse(entity, statusLine.getStatusCode(), statusLine.getReasonPhrase());
			}
			finally {
				EntityUtils.consumeQuietly(entity);
			}
		}
		catch(InterruptedIOException e) {
			lastResponseTime = 0;
			throw e;
		}
	}

	private Result processResponse(HttpEntity entity, int statusCode, String reasonPhrase) throws IOException {
		if(statusCode == HttpStatus.SC_OK) {
			ContentType contentType = ContentType.get(entity);
			if(contentType != null) {
				if(contentType.getMimeType().equals(ContentType.APPLICATION_JSON.getMimeType())) {
					return processJsonResponse(entity);
				}
			}
		}
		return new StatusResult(statusCode, reasonPhrase);
	}

	private Result processJsonResponse(HttpEntity entity) throws IOException {
		InputStreamReader reader = new InputStreamReader(entity.getContent(), Consts.UTF_8);
		JSONTokener tokener = new JSONTokener(reader);
		try {
			Object jsonValue = tokener.nextValue();
			return new JsonResult(jsonValue);
		}
		catch(JSONException e) {
			throw new StreamCorruptedException("Unable to process received JSON");
		}
	}

	private UrlEncodedFormEntity makeUrlEncodedEntity(String[] nameValues) {
		int numOfQueryFields = nameValues.length >> 1;
		List<NameValuePair> pairs = new ArrayList<NameValuePair>(numOfQueryFields);
		for(int fieldIndex = 0; fieldIndex < numOfQueryFields; fieldIndex++) {
			BasicNameValuePair pair = new BasicNameValuePair(nameValues[fieldIndex * 2], nameValues[fieldIndex * 2 + 1]);
			pairs.add(pair);
		}
		UrlEncodedFormEntity entity = new UrlEncodedFormEntity(pairs, Consts.UTF_8);
		return entity;
	}
}
